﻿using System;
using System.ComponentModel;
using System.ServiceModel.DomainServices.Client;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace RiaMVVM
{
	/// <summary>
	/// A base class for a RiaQuery Opperation.
	/// Unless you are making some sort of crazy multi-context query factory, you would probably be
	/// better off using one of the other abstract base classes...
	/// </summary>
	public abstract class RiaQuery : INotifyPropertyChanged
	{
		#region INotifyPropertyChanged values

		public event PropertyChangedEventHandler PropertyChanged;

		protected void RaisePropertyChanged(string propertyName)
		{
			var handler = PropertyChanged;
			if (handler != null)
			{
				handler(this, new PropertyChangedEventArgs(propertyName));
			}
		}
		#endregion

		#region Query

		private EntityQuery _Query;

		public EntityQuery Query
		{
			get { return _Query; }
			private set
			{
				if (_Query != value)
				{
					_Query = value;
					RaisePropertyChanged("Query");
				}
			}
		}

		#endregion

		#region Result

		private IEnumerable _Result;

		public IEnumerable Result
		{
			get { return _Result; }
			private set
			{
				if (_Result != value)
				{
					_Result = value;
					RaisePropertyChanged("Result");
				}
			}
		}

		#endregion

		#region ExecuteBusy

		private bool _ExecuteBusy;

		public bool ExecuteBusy
		{
			get { return _ExecuteBusy; }
			private set
			{
				if (_ExecuteBusy != value)
				{
					_ExecuteBusy = value;
					RaisePropertyChanged("ExecuteBusy");
				}
			}
		}

		#endregion

		#region ExecuteCommand

		private DelegateCommand _ExecuteCommand;

		public DelegateCommand ExecuteCommand
		{
			get
			{
				if (_ExecuteCommand == null)
				{
					_ExecuteCommand = new DelegateCommand(o => Execute(o), o => !ExecuteBusy);
				}
				return _ExecuteCommand;
			}
		}

		#endregion

		#region ExecuteException

		private Exception _ExecuteException;

		public Exception ExecuteException
		{
			get { return _ExecuteException; }
			private set
			{
				if (_ExecuteException != value)
				{
					_ExecuteException = value;
					RaisePropertyChanged("ExecuteException");
				}
			}
		}

		#endregion

		public event EventHandler ExecuteBegin;
		public event EventHandler ExecuteComplete;
		public event EventHandler ExecuteSuccess;
		public event EventHandler ExecuteError;

		protected DomainContext Context;

		/// <summary>
		/// Do we recreate the query for each call to the server?
		/// </summary>
		public bool CreateQueryEachTime { get; set; }

		protected RiaQuery(DomainContext context)
		{
			if (context == null) throw new ArgumentException("context");
			Context = context;
		}

		public void Execute(object param)
		{
			ExecuteBusy = true;

			if (ExecuteBegin != null)
				ExecuteBegin(this, EventArgs.Empty);

			if (CreateQueryEachTime || Query == null)
				CreateQueryInternal();

			Context.Load(Query, LoadBehavior.RefreshCurrent, o =>
			{
				if (o.HasError)
				{
					ExecuteException = o.Error;
					if (ExecuteError != null)
						ExecuteError(this, EventArgs.Empty);
					o.MarkErrorAsHandled();
				}
				else
				{
					Result = o.Entities;
					if (ExecuteSuccess != null)
						ExecuteSuccess(this, EventArgs.Empty);
				}

				if (ExecuteComplete != null)
					ExecuteComplete(this, EventArgs.Empty);

				ExecuteBusy = false;

			}, false);
		}

		private void CreateQueryInternal()
		{
			Query = CreateQuery();
		}

		/// <summary>
		/// You must override this to provide an EntityQuery for your result.
		/// </summary>
		/// <returns></returns>
		protected abstract EntityQuery CreateQuery();
	}

	/// <summary>
	/// A generic base class for a RiaQuery Opperation with strongly typed DomainContext.
	/// This is intended to be used in a factory pattern where you don't really know which query you are going to be calling.
	/// </summary>
	/// <typeparam name="TContext"></typeparam>
	public abstract class RiaQuery<TContext> : RiaQuery
		where TContext : DomainContext
	{
		new protected TContext Context
		{
			get { return base.Context as TContext; }
			set { base.Context = value; }
		}

		protected RiaQuery(TContext context) : base(context) { }
	}

	/// <summary>
	/// A generic base class for a RiaQuery Opperation with strongly typed DomainContext and Entity
	/// </summary>
	/// <typeparam name="TContext"></typeparam>
	/// <typeparam name="TEntity"></typeparam>
	public abstract class RiaQuery<TContext, TEntity> : RiaQuery<TContext>
		where TContext : DomainContext
		where TEntity : Entity
	{
		new public EntityQuery<TEntity> Query
		{
			get { return base.Query as EntityQuery<TEntity>; }
		}

		new public IEnumerable<TEntity> Result
		{
			get { return base.Result == null ? null : base.Result.OfType<TEntity>(); }
		}

		protected RiaQuery(TContext context) : base(context) { }
	}
}
